package models

import (
	"errors"

	"gorm.io/gorm"
	"wowCoupon.com/pkg/e"
)

type CouponMeta struct {
	Model

	//MetaNumber int `json:"meta_number" gorm:"index"`
	BusinessId     int64  `json:"business_id"        gorm:"not null"`
	GoodId         int64  `json:"good_id"            gorm:"not null"`
	Type           int64  `json:"type"               gorm:"not null"`
	Title          string `json:"title"              gorm:"not null"`
	Amount         int64  `json:"amount"             gorm:"not null"`
	RemainAmount   int64  `json:"remain_amount"      gorm:"not null"`
	Comments       string `json:"comments"           gorm:"default:-"`
	IssueStartTime int64  `json:"issue_start_time"   gorm:"not null"`
	IssueEndTime   int64  `json:"issue_end_time"     gorm:"not null"`
	ValidStartTime int64  `json:"valid_start_time"   gorm:"not null"`
	ValidEndTime   int64  `json:"valid_end_time"     gorm:"not null"`
	Status         int64  `json:"status"             gorm:"not null"`
	CreatedBy      string `json:"created_by"         gorm:"not null"`
	EditedBy       string `json:"edited_by"          gorm:"not null"`

	//Optimistic lock, used for update
	Version int64 `json:"version"          gorm:"not null"`
}

// AddCouponMeta add a single CouponMeta
func AddCouponMeta(data map[string]interface{}) (id int64, err error) {
	couponMeta := CouponMeta{
		BusinessId:     data["business_id"].(int64),
		GoodId:         data["good_id"].(int64),
		Type:           data["type"].(int64),
		Title:          data["title"].(string),
		Amount:         data["amount"].(int64),
		RemainAmount:   data["remain_amount"].(int64),
		Comments:       data["comments"].(string),
		IssueStartTime: data["issue_start_time"].(int64),
		IssueEndTime:   data["issue_end_time"].(int64),
		ValidStartTime: data["valid_start_time"].(int64),
		ValidEndTime:   data["valid_end_time"].(int64),
		Status:         data["status"].(int64),
		CreatedBy:      data["created_by"].(string),
		EditedBy:       data["edited_by"].(string),
		Version:        1,
	}

	if err = db.Create(&couponMeta).Error; err != nil {
		return 0, err
	}

	return int64(couponMeta.ID), nil
}

func AddCouponMetas(cs []CouponMeta) (int, error) {
	if err := db.Create(cs).Error; err != nil {
		return 0, err
	}

	return len(cs), nil
}

// UpdateCouponMetaStatus updates a coupon meta's status
func UpdateCouponMetaStatus(id int64, status int64) error {
	for i := 0; i < 5; i++ {
		c, _ := GetCouponMetaByID(id)

		if status == c.Status {
			// No need to update
			return nil
		}

		tx := db.Exec(
			"update coupon_meta set status = ?, version = ? where id = ? and status <= ? and version = ?",
			status, c.Version+1,
			id, status, c.Version,
		)

		if err := tx.Error; err != nil {
			return err
		}

		if tx.RowsAffected <= 0 {
			continue
		}

		// successfully update
		return nil
	}

	// failed update after 5 try
	return e.ErrNotUpdated
}

// UpdateCouponMetaRemainAmount updates a coupon meta's remain amount
func UpdateCouponMetaRemainAmount(id int64, status int64, remainAmount int64) error {
	tx := db.Exec(
		"update coupon_meta set status = ?, remain_amount = ? where id = ? and status <= ?",
		status, remainAmount,
		id, status,
	)

	if err := tx.Error; err != nil {
		return err
	}

	/*if tx.RowsAffected <= 0 {
		return e.ErrNotUpdated
	}*/

	return nil
}

// UpdatesCouponMeta updates a *CouponMeta
func UpdatesCouponMeta(c *CouponMeta) error {
	tx := db.Model(c).Where("status <= ?", c.Status).Updates(c)
	if err := tx.Error; err != nil {
		return err
	}

	if tx.RowsAffected <= 0 {
		return errors.New("data not been update")
	}

	return nil
}

// UpdatesCouponMetas updates []*CouponMeta
func UpdatesCouponMetas(couponMetas []*CouponMeta) error {
	for _, couponMeta := range couponMetas {
		if err := UpdatesCouponMeta(couponMeta); err != nil {
			return err
		}
	}

	return nil
}

// UpdatesCouponMetaByID modifies a single CouponMeta by id
func UpdatesCouponMetaByID(id int64, data interface{}) error {
	if err := db.Model(&CouponMeta{}).
		Where("id = ? and status = ?", id, e.COUPON_STATUS_NOT_START).
		Updates(data).Error; err != nil {
		return err
	}

	return nil
}

// UpdatesCouponMetaByConstraints modifies a single CouponMeta by constraints
func UpdatesCouponMetaByConstraints(constraints map[string]interface{}, data interface{}) error {
	if err := db.Model(&CouponMeta{}).
		Where("status = ?", e.COUPON_STATUS_NOT_START).
		Where(constraints).
		Updates(data).Error; err != nil {

		return err
	}

	return nil
}

// UpdatesCouponMetaByStringConditions modifies a single CouponMeta by string conditions
func UpdatesCouponMetasByStringConditions(conditions []string, data interface{}) error {
	if err := db.Model(&CouponMeta{}).
		Where("status = ?", e.COUPON_STATUS_NOT_START).
		Where(conditions).
		Updates(data).Error; err != nil {

		return err
	}

	return nil
}

// DeleteCouponMeta deletes a *CouponMeta
func DeleteCouponMeta(c *CouponMeta) error {
	if err := db.Where("status in ?", []int{e.COUPON_STATUS_END, e.COUPON_STATUS_NOT_START}).
		Delete(c).Error; err != nil {

		return err
	}

	return nil
}

// DeleteCouponMetas deletes []*CouponMeta
func DeleteCouponMetas(couponMetas []*CouponMeta) error {
	if err := db.Where("status in ?", []int{e.COUPON_STATUS_END, e.COUPON_STATUS_NOT_START}).
		Delete(couponMetas).Error; err != nil {

		return err
	}

	return nil
}

// DeleteCouponMeta delete a single CouponMeta by id
func DeleteCouponMetaByID(id int64) error {
	if err := db.Where("id = ? and status in ?", id, []int{e.COUPON_STATUS_END, e.COUPON_STATUS_NOT_START}).
		Delete(CouponMeta{}).Error; err != nil {

		return err
	}

	return nil
}

// DeleteCouponMeta deletes CouponMetas by constraints
func DeleteCouponMetasByConstraints(constraints map[string]interface{}) error {
	if err := db.Where("status in ?", []int{e.COUPON_STATUS_END, e.COUPON_STATUS_NOT_START}).
		Where(constraints).
		Delete(CouponMeta{}).Error; err != nil {

		return err
	}

	return nil
}

// DeleteCouponMeta deletes a soft deleted *CouponMeta permanently
func DeleteCouponMetaPerm(c *CouponMeta) error {
	if err := db.Unscoped().Where("deleted_at != 0").Delete(c).Error; err != nil {
		return err
	}

	return nil
}

// DeleteCouponMetas deletes soft deleted []*CouponMeta permanently
func DeleteCouponMetasPerm(couponMetas []*CouponMeta) error {
	if err := db.Unscoped().Where("deleted_at != 0").Delete(couponMetas).Error; err != nil {
		return err
	}

	return nil
}

// DeleteCouponMeta delete a single soft deleted CouponMeta by id permanently
func DeleteCouponMetaByIDPerm(id int) error {
	if err := db.Unscoped().Where("id = ? and deleted_at != 0", id).Delete(CouponMeta{}).Error; err != nil {
		return err
	}

	return nil
}

// DeleteCouponMeta delete soft deleted CouponMetas by constraints permanently
// If constraints is nil, all the soft deleted CouponMetas will be deleted permanently
func DeleteCouponMetasByConstraintsPerm(constraints map[string]interface{}) error {
	if err := db.Unscoped().Where("deleted_at != 0").Where(constraints).Delete(CouponMeta{}).Error; err != nil {
		return err
	}

	return nil
}

// ExistCouponMetaByID checks if an CouponMeta exists based on id
func ExistCouponMetaByID(id int) (bool, error) {
	var couponMeta CouponMeta

	err := db.Select("id").Where("id = ?", id).First(&couponMeta).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return false, err
	}

	if couponMeta.ID > 0 {
		return true, nil
	}

	return false, nil
}

// ExistCouponMetaByConstraints checks if an CouponMeta exists based on constraints
func ExistCouponMetaByConstraints(constraints map[string]interface{}) (bool, error) {
	var couponMeta CouponMeta

	err := db.Select("id").Where(constraints).First(&couponMeta).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return false, err
	}

	if couponMeta.ID > 0 {
		return true, nil
	}

	return false, nil
}

// GetCouponMetasTotal gets the total number of CouponMetas
func GetCouponMetasTotal() (int64, error) {
	var count int64

	if err := db.Model(&CouponMeta{}).Count(&count).Error; err != nil {
		return 0, err
	}

	return count, nil
}

// GetCouponMetaTotal gets the number of CouponMetas by constraints
func GetCouponMetasNumberByConstraints(constraints map[string]interface{}) (int64, error) {
	var count int64

	if err := db.Model(&CouponMeta{}).Where(constraints).Count(&count).Error; err != nil {
		return 0, err
	}

	return count, nil
}

// GetCouponMetaByID gets a single CouponMeta by ID
func GetCouponMetaByID(id int64) (*CouponMeta, error) {
	var couponMeta CouponMeta

	err := db.Where("id = ?", id).First(&couponMeta).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}

	return &couponMeta, nil
}

// GetCouponMeta Get a single CouponMeta by constraints
func GetCouponMetaByConstraints(constraints map[string]interface{}) (*CouponMeta, error) {
	var couponMeta CouponMeta

	err := db.Where(constraints).First(&couponMeta).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}

	return &couponMeta, nil
}

// GetCouponMetas gets a list of CouponMetas based on paging constraints.
// If you don not need offset or limit, just set them to -1
func GetCouponMetasByConstraints(offset int, limit int, constraints map[string]interface{}) ([]*CouponMeta, error) {
	var couponMetas []*CouponMeta

	err := db.Where(constraints).Offset(offset).Limit(limit).Find(&couponMetas).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}

	return couponMetas, nil
}

// GetCouponMetas gets a list of CouponMetas based on string conditions
func GetCouponMetasByStringConditions(conditions []string) ([]*CouponMeta, error) {
	var couponMetas []*CouponMeta

	err := db.Where(conditions[0]).Find(&couponMetas).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}

	return couponMetas, nil
}

// GetCouponMeta Get a single CouponMeta by ID
func GetSoftDeletedCouponMetaByID(id int64) (*CouponMeta, error) {
	var couponMeta CouponMeta

	err := db.Unscoped().Where("id = ?", id).First(&couponMeta).Error

	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}

	return &couponMeta, nil
}

func Editable(c *CouponMeta) bool {
	if c.Status == e.COUPON_STATUS_NOT_START {
		return true
	}

	return false
}
